/* mmain.c
 * Main program
 *
 * $Id:  $
 *
 * Xplico System
 * By Gianluca Costa <g.costa@xplico.org>
 * Copyright 2007-2011 Gianluca Costa & Andrea de Franceschi. Web: www.xplico.org
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <net/if.h>
#include <sys/time.h>
#include <netdb.h>
#include <errno.h>

#include "log.h"
#include "dis_mod.h"
#include "proto.h"
#include "flow.h"
#include "capture.h"
#include "dispatch.h"
#include "version.h"
#include "dmemory.h"
#include "fthread.h"
#include "report.h"
#include "strutil.h"
#include "config_param.h"
#include "embedded.h"
#include "pei.h"
#include "mpei.h"
#include "disp_manipula.h"
#include "geoiploc.h"
#include "analyse.h"
#include "manipula.h"
#include "dnsdb.h"

#define MNP_BUFFER_SIZE       1024

extern int LogDirName(char *file_cfg); /* log.c */
extern int LogToScreen(bool enb);      /* log.c */
extern void CommonLink(void);          /* common/link.c */
extern void DissectorLink(void);       /* dissector/link.c */

extern volatile pei *volatile mnp_pei;

static int sock;   /* connection socket */
static int sd;     /* server socket */

static void Usage(char *name)
{
    printf("\n");
    printf("usage: %s [-h] [-s] [-l] [-i] [-c <config_file>] -p <port>\n", name);
    printf("\t-c config file\n");
    printf("\t-s silent\n");
    printf("\t-p connection port\n");
    printf("\t-i info (PEI generated by this manipulator)\n");
    printf("\t-l print all log in the screen\n");
    printf("\t-h this help\n");
    printf("\tNOTE: parameters MUST respect this order!\n");
    printf("\n");
}


static int CoreLog(const char *file_cfg)
{
    FILE *fp;
    int nl;
    char buffer[CFG_LINE_MAX_SIZE];
    char bufcpy[CFG_LINE_MAX_SIZE];
    char mask[CFG_LINE_MAX_SIZE];
    char *param;
    unsigned short logm;
    int res;

    /* find directory location of module from config file */
    fp = fopen(file_cfg, "r");
    if (fp == NULL) {
        LogPrintf(LV_ERROR, "Config file can't be opened");
        return -1;
    }

    nl = 0;
    while (fgets(buffer, CFG_LINE_MAX_SIZE, fp) != NULL) {
        nl++;
        /* check all line */
        if (strlen(buffer)+1 == CFG_LINE_MAX_SIZE) {
            LogPrintf(LV_ERROR, "Config file line more length to %d characters", CFG_LINE_MAX_SIZE);
            fclose(fp);

            return -1;
        }
        /* check if line is a comment */
        if (!CfgParIsComment(buffer)) {
            /* log mask */
            param = strstr(buffer, CFG_PAR_CORE_LOG"=");
            if (param != NULL) {
                res = sscanf(param, CFG_PAR_CORE_LOG"=%s %s", mask, bufcpy);
                logm = LV_BASE;
                if (res > 0) {
                    if (res == 2 && !CfgParIsComment(bufcpy)) {
                        LogPrintf(LV_ERROR, "Config param error in line %d. Unknow param: %s", nl, bufcpy);
                        fclose(fp);

                        return -1;
                    }
                    logm |= CfgParLogMask(mask, nl);
                }
                else {
                    LogPrintf(LV_ERROR, "Config param error in line %d. Unknow param: %s", nl, buffer);
                    fclose(fp);

                    return -1;
                }
                /* set mask */
                LogSetMask(LOG_COMPONENT, logm);
            }
        }
    }
    fclose(fp);

    return 0;
}


static char *ManipConnect(unsigned short port)
{
    char *cfg_mdls;
    struct addrinfo hints, *servinfo, *add;
    char sport[25];
    int rv, yes, opts, dim;
    int sd4, sd6, maxsd;
    fd_set sd_set, cp_set;
    struct sockaddr_storage their_addr;
    socklen_t sin_size;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;        /* use my IP */
    hints.ai_protocol = IPPROTO_TCP;
    sprintf(sport, "%i", port);

    rv = getaddrinfo(NULL, sport, &hints, &servinfo);
    if (rv != 0) {
	printf("getaddrinfo() failed, %s\n", gai_strerror(rv));
        return NULL;
    }
    sd4 = sd6 = 0;
    for (add = servinfo; add != NULL; add = add->ai_next) {
        if (add->ai_family == AF_INET) {
            if (sd4 == 0) {
                sd4 = socket(add->ai_family, add->ai_socktype, add->ai_protocol); 
		if (sd4 == -1) {
		    sd4 = 0;
		    continue;
		}
                
                yes = 1;
                if (setsockopt(sd4, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof (yes)) < 0) {
                    printf("setsockopt\n");
                    close(sd4);
                    return NULL;
                }
#ifdef SO_REUSEPORT
                if (setsockopt(sd4, SOL_SOCKET, SO_REUSEPORT, (char *) &yes, sizeof(yes)) < 0) {
                    perror("SO_REUSEPORT");
                    close(sd4);
                    return NULL;
                }
#endif
                opts = fcntl(sd4, F_GETFL);
                if (opts < 0) {
                    perror("fcntl(F_GETFL) failed");
                    close(sd4);
                    return NULL;
                }
                opts = opts | O_NONBLOCK;
                if (fcntl(sd4, F_SETFL, opts) < 0) {
                    perror("fcntl(F_SETFL) failed");
                    close(sd4);
                    return NULL;
                }
                rv = bind(sd4, add->ai_addr, add->ai_addrlen);
                if (rv == -1) {
                    printf("Cannot bind port\n");
                    close(sd4);
                    return NULL;
                }
            }
        }
        else if (add->ai_family == AF_INET6) {
            if (sd6 == 0) {
                sd6 = socket(add->ai_family, add->ai_socktype, add->ai_protocol); 
        		if (sd6 == -1) {
                    sd6 = 0;
                    continue;
                }
                
                yes = 1;
                if (setsockopt(sd6, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof (yes)) < 0) {
                    printf("setsockopt\n");
                    close(sd6);
                    return NULL;
                }
#ifdef SO_REUSEPORT
                if (setsockopt(sd6, SOL_SOCKET, SO_REUSEPORT, (char *) &yes, sizeof(yes)) < 0) {
                    perror("SO_REUSEPORT");
                    close(sd6);
                    return NULL;
                }
#endif
                if (setsockopt(sd6, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) < 0) {
                    perror("IPV6_V6ONLY");
                    close(sd6);
                    return NULL;
                }
                
                opts = fcntl(sd6, F_GETFL);
                if (opts < 0) {
                    perror("fcntl(F_GETFL) failed");
                    close(sd6);
                    return NULL;
                }
                opts = opts | O_NONBLOCK;
                if (fcntl(sd6, F_SETFL, opts) < 0) {
                    perror("fcntl(F_SETFL) failed");
                    close(sd6);
                    return NULL;
                }
                rv = bind(sd6, add->ai_addr, add->ai_addrlen);
                if (rv == -1) {
                    printf("Cannot bind port\n");
                    close(sd6);
                    return NULL;
                }
            }
        }
    }

    if (sd4 == 0 && sd6 == 0) {
        printf("Unable to bind to either IPv4 or IPv6 address\n");
        return NULL;
    }
    
    if (sd4 != 0) {
        listen(sd4, 1);  /* only one connection */ 
    }
    if (sd6 != 0) {
        listen(sd6, 1);  /* only one connection */ 
    }
    FD_ZERO(&sd_set);
    if (sd4 != 0)
        FD_SET(sd4, &sd_set);
    if (sd6 != 0)
        FD_SET(sd6, &sd_set);
    maxsd = sd4;
    if (sd6 > maxsd)
        maxsd = sd6;
    while (1) {
        memcpy(&cp_set, &sd_set, sizeof(sd_set));
        rv = select(maxsd + 1, &cp_set, NULL, NULL, NULL);
	if (rv < 0) {
            if (errno == EINTR)
                continue;
            
            if (sd4 != 0)
                close(sd4);
            if (sd6 != 0)
                close(sd6);
            
            return NULL;
        }

        if (sd4 != 0 && FD_ISSET(sd4, &cp_set)) {
            sin_size = sizeof(their_addr);
            sock = accept(sd4, (struct sockaddr *)&their_addr, &sin_size);
            if (sock == -1)
                continue;
            opts = fcntl(sd4, F_GETFL);
            if (opts < 0) {
                perror("fcntl(F_GETFL) failed");
                continue;
            }
            opts = (opts & (~O_NONBLOCK));
            if (fcntl(sd4, F_SETFL, opts) < 0) {
                perror("fcntl(F_SETFL) failed");
                continue;
            }
            
            sd = sd4;
            close(sd6);
            break;
        }
        if (sd6 != 0 && FD_ISSET(sd6, &cp_set)) {
            sin_size = sizeof(their_addr);
            sock = accept(sd6, (struct sockaddr *)&their_addr, &sin_size);
            if (sock == -1)
                continue;
            opts = fcntl(sd6, F_GETFL);
            if (opts < 0) {
                perror("fcntl(F_GETFL) failed");
                continue;
            }
            opts = (opts & (~O_NONBLOCK));
            if (fcntl(sd6, F_SETFL, opts) < 0) {
                perror("fcntl(F_SETFL) failed");
                continue;
            }
            
            sd = sd6;
            close(sd4);
            break;
        }
    }
    
    /* wait cfg file for modules list */
    read(sock, &dim, sizeof(int));
    cfg_mdls = xmalloc(dim + 1);
    read(sock, cfg_mdls, dim);
    cfg_mdls[dim] = '\0';
    printf("Connession from: %s cfg: %s\n", inet_ntoa(((struct sockaddr_in *)&their_addr)->sin_addr), cfg_mdls);

    return cfg_mdls;
}


static void ManipConClose(void)
{
    /* close connection */
    close(sock);
    close(sd);
}


static int ManipLoop(void)
{
    pei *ppei;
    unsigned long tot_pei;

    /* init analyzer */
    AnalyseInit();
    tot_pei = 0;
    while (1) {
        ppei = DispManipGetPei(sock);
        mnp_pei = ppei;
        if (ppei != NULL) {
            tot_pei++;
            AnalysePei(ppei);
            mnp_pei = NULL;
        }
        else
            break;
    }
    
    /* stop/end analyzer */
    AnalyseEnd();

    return tot_pei;
}


int main(int argc, char *argv[])
{
    bool help, flag, log, info;
    int c, prot_id;
    char config_file[MNP_BUFFER_SIZE];
    unsigned short port;
    struct timeval start_t, end_t;
    extern char *optarg;
    extern int optind, optopt;
    char *cfg_mdls;
    unsigned long tot_pei;

    help = FALSE;
    flag = FALSE;
    log = FALSE;
    port = 0;
    info = FALSE;
    
    gettimeofday(&start_t, NULL);
    strcpy(config_file, "config/"MANIPULA_CFG_NAME); /* default */
    while ((c = getopt(argc, argv, "c:hsip:l")) != -1) {
        switch(c) {
        case 'c':
            snprintf(config_file, MNP_BUFFER_SIZE, "%s", optarg);
            config_file[MNP_BUFFER_SIZE-1] = '\0';
            break;
            
        case 's':
            close(1);
            break;

        case 'p':
            flag = TRUE;
            port = atoi(optarg);
            break;
            
        case 'h':
            help = TRUE;
            break;

        case 'i':
            flag = TRUE;
            info = TRUE;
            break;
            
        case 'l':
            log = TRUE;
            break;

        case '?':
            printf("Error: unrecognized option: -%c\n", optopt);
            Usage(argv[0]);
            exit(2);
            break;
        }
    }
    
    printf(MANIPULA_NAME" v%d.%d.%d\n", XPLICO_VER_MAG, XPLICO_VER_MIN, XPLICO_VER_REV);
    printf("%s\n", XPLICO_CR);

#ifdef GEOIP_LIBRARY
    printf("%s\n", XPLICO_GEOP_LICENSE);
#endif

    /* help & check */
    if (help || flag == FALSE) {
        Usage(argv[0]);
        return 0;
    }

    /* common functions initialization (embeded, strutil,...)*/
    CommonLink();

    /* common dissector linking funcions */
    DissectorLink();

    /* log dir */
    if (LogDirName(config_file) == -1) {
        /* change configuration file */
        strcpy(config_file, "/opt/xplico/cfg/"MANIPULA_CFG_NAME); /* next default */
        if (LogDirName(config_file) == -1) {
            printf("Configuration file execution error (see above).\n");
            return -1;
        }
        else {
            printf("Configuration file found!\n");
        }
    }
    else {
        printf("Configuration file found!\n");
    }

    /* log screen setting */
    LogToScreen(log);

    /* core log mask */
    if (CoreLog(config_file)  == -1) {
        LogPrintf(LV_FATAL, "Log setup failed");
        exit(-1);
    }

    /* memory function initialization  */
    if (DMemInit() == -1) {
        LogPrintf(LV_FATAL, "Memory initialization failed");
        exit(-1);
    }
    
    /* Thread function initialization */
    FthreadInit(config_file);

    /* connection start-wait */
    if (info == FALSE) {
        cfg_mdls = ManipConnect(port);
        if (cfg_mdls == NULL) {
            LogPrintf(LV_FATAL, "No connection with xplico");
            exit(-1);
        }
    }
    else {
        /* used only for display -i information */
        cfg_mdls = "config/xplico_cli.cfg";
    }

    /* load modules */
    if (DisModLoad(cfg_mdls) == -1) {
        LogPrintf(LV_FATAL, "Load modules failed");
        exit(-1);
    }

    /* tmp dir */
    ManipTmpDir(config_file);

    /* pei components registration of manipulator */
    prot_id = ProtId(MANIPULA_PROCOL);
    ManipPeiProtocol(prot_id);
    ManipPeiComponent();
    ManipPeiRegister();

    /* protcol information & pei components */
    if (info) {
        DisModProtInfo(MANIPULA_PROCOL);
        return 0;
    }

    /* load dispatch */
    if (DispatchInit(config_file)  == -1) {
        LogPrintf(LV_FATAL, "Load 'dispatch' module failed");
        exit(-1);
    }

    /* initializatiion modules */
    if (DisModInit() == -1) {
        LogPrintf(LV_FATAL, "Modules initialization failed");
        exit(-1);
    }

    /* Dns db init */
    DnsDbInit();

    /* Geo Ip localization init */
    if (GeoIPLocInit(config_file) == -1) {
        printf("Download GeoLiteCity.dat from http://geolite.maxmind.com/download/geoip/database/ and gunzip\n");
        sleep(2);
    }
    else {
        printf("GeoLiteCity.dat found!\n");
    }

    /* start */
    tot_pei = ManipLoop();

    /* close connection */
    ManipConClose();
    
    /* close modules dissectors */
    DisModClose();

    /* close dispacher */
    DispatchEnd();

    gettimeofday(&end_t, NULL);
    
    LogPrintf(LV_STATUS, "Total PEI received: %lu", tot_pei);
    LogPrintf(LV_STATUS, "End. Total elaboration time:  %lus %luus", end_t.tv_sec-start_t.tv_sec+(1000000+end_t.tv_usec-start_t.tv_usec)/1000000, (1000000+end_t.tv_usec-start_t.tv_usec)%1000000);

    return 0;
}
